/* $ZEL: sis1100_irq_handler.c,v 1.10 2006/03/10 12:25:15 wuestner Exp $ */

/*
 * Copyright (c) 2001-2004
 * 	Peter Wuestner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "sis1100_sc.h"

#if !defined(__NetBSD__) && ! defined(__linux__)
#error Wrong Operating System
#endif

int
sis1100_irq_ctl(struct sis1100_softc* sc, struct sis1100_fdata* fd,
        struct sis1100_irq_ctl* data)
{
    int foreign_irqs;
    struct list_head* curr;
    u_int32_t mask;

/*pINFO(sc, "irq_ctl: signal=%d mask=0x%x", data->signal, data->irq_mask);*/

    if (data->signal) {
        foreign_irqs=0;
        SEM_LOCK(sc->sem_fdata_list);
        /* irq already in use? */
        list_for_each(curr, &sc->fdata_list_head) {
            struct sis1100_fdata* fd;
            fd=list_entry(curr, struct sis1100_fdata, list);
            foreign_irqs |= fd->owned_irqs;
        }
        SEM_UNLOCK(sc->sem_fdata_list);
        if (foreign_irqs & data->irq_mask) {
            pINFO(sc, "irq_ctl: IRQs owned by other programs: 0x%08x\n",
                    foreign_irqs);
            return  EBUSY;
        }

#ifdef __NetBSD__
        fd->pid=fd->p->p_pid;
#elif __linux__
        fd->pid=current->pid;
#endif
        fd->sig=data->signal;
        fd->owned_irqs |= data->irq_mask;
        fd->old_remote_hw=sc->remote_hw;

        switch (sc->remote_hw) {
        case sis1100_hw_vme:
            sis3100rem_enable_irqs(sc, fd, data->irq_mask);
            break;
        case sis1100_hw_camac:
            sis5100rem_enable_irqs(sc, fd, data->irq_mask);
            break;
        case sis1100_hw_pci:     break; /* do nothing */
        case sis1100_hw_lvd:     break; /* nothing to be done */
        case sis1100_hw_invalid: break; /* do nothing */
        }
        if (sc->remote_hw!=sis1100_hw_lvd) {
        /* this is a bad hack for sis1100_hw_lvd
         * sis1100_hw_lvd needs all 32 bit of the doorbell register
         * we should have a second mask for all other irq sources
         */
            /* enable PCI-FRONT-IRQs and MBX0_IRQ */
            mask=0;
            if (data->irq_mask & SIS1100_FRONT_IRQS) {
                mask|=(data->irq_mask & SIS1100_FRONT_IRQS)>>4;
            }
            if (data->irq_mask & SIS1100_MBX0_IRQ) {
                mask|=0x400;
            }
            if (mask) sis1100_enable_irq(sc, 0, mask);
        }
    } else {
        int irqs;
        irqs=fd->owned_irqs & data->irq_mask;

        switch (sc->remote_hw) {
        case sis1100_hw_vme:
            sis3100rem_disable_irqs(sc, fd, irqs);
            break;
        case sis1100_hw_camac:
            sis5100rem_disable_irqs(sc, fd, irqs);
            break;
        case sis1100_hw_pci:     break; /* do nothing */
        case sis1100_hw_lvd:     break; /* do nothing */
        case sis1100_hw_invalid: break; /* do nothing */
        }
        /* disable PCI-FRONT-IRQs and MBX0_IRQ */
        mask=0;
        if (irqs & SIS1100_FRONT_IRQS) {
            mask|=(irqs & SIS1100_FRONT_IRQS)>>4;
        }
        if (irqs & SIS1100_MBX0_IRQ) {
            mask|=irq_mbx0;
        }
        if (mask) sis1100_disable_irq(sc, 0, mask);

        fd->owned_irqs &= ~data->irq_mask;
    }
    /*pINFO(sc, "irq_ctl: sig=%d owned_irqs=0x%x old_remote_hw=%d",
        fd->sig, fd->owned_irqs, fd->old_remote_hw);*/

    return 0;
}

int
sis1100_irq_ack(struct sis1100_softc* sc, struct sis1100_fdata* fd,
        struct sis1100_irq_ack* data)
{
    int irqs;

    /*
    pINFO(sc, "irq_ack: mask=0x%x", data->irq_mask);
    */
    SEM_LOCK(sc->sem_irqinfo);

    irqs=fd->owned_irqs & data->irq_mask & sc->pending_irqs;
    sc->pending_irqs&=~irqs;

    switch (sc->remote_hw) {
    case sis1100_hw_vme:
        sis3100rem_irq_ack(sc, irqs);
        break;
    case sis1100_hw_camac:
        sis5100rem_irq_ack(sc, irqs);
        break;
    case sis1100_hw_pci:     break; /* do nothing */
    case sis1100_hw_lvd:
        if (irqs&0x3fffffff)
            zellvd_rem_irq_ack(sc, irqs);
        break;
    case sis1100_hw_invalid: break; /* do nothing */
    }

    SEM_UNLOCK(sc->sem_irqinfo);
    return 0;
}

int
sis1100_irq_get(struct sis1100_softc* sc, struct sis1100_fdata* fd,
        struct sis1100_irq_get* data)
{
/*static int lastblock=-1;*/

    SEM_LOCK(sc->sem_irqinfo);

    data->irqs=sc->pending_irqs & fd->owned_irqs;
    if (fd->old_remote_hw!=sc->remote_hw) {
        if (sc->remote_hw!=sis1100_hw_invalid)
            data->remote_status=1;
        else
            data->remote_status=-1;
        fd->old_remote_hw=sc->remote_hw;
    } else {
        data->remote_status=0;
    }

    data->opt_status=sc->last_opt_csr;
    data->mbx0=sc->mbx0;

    data->level=0;
    data->vector=0;
    switch (sc->remote_hw) {
    case sis1100_hw_vme:
        if (data->irqs & SIS3100_VME_IRQS)
            sis3100rem_get_vector(sc, data->irqs & data->irq_mask, data);
        break;
    case sis1100_hw_lvd:
        {
        if (sc->demand_dma.status==dma_running) {
            /*
            if (sc->demand_dma.last_block<=lastblock) {
                pERROR(sc, "irq_get block %d --> %d",
                        lastblock, sc->demand_dma.last_block);
            }
            lastblock=sc->demand_dma.last_block;
            */
            if (data->irqs&(1<<30))
                data->vector=sc->demand_dma.last_block;
            }
        }
        break;
    default: /* do nothing */
        {}
    }

    SEM_UNLOCK(sc->sem_irqinfo);
    return 0;
}

int
sis1100_irq_wait(struct sis1100_softc* sc, struct sis1100_fdata* fd,
        struct sis1100_irq_get* data)
{
    int res=0;
/*
    pINFO(sc, "irq_wait: pending=0x%x mask=0x%x",
            sc->pending_irqs, data->irq_mask);
*/
#ifdef __NetBSD__
    {
    int s;
    s = splbio();
    while (!(res || irq_pending(sc, fd, data->irq_mask))) {
        res = tsleep(&sc->remoteirq_wait, PCATCH, "sis1100_irq", 0);
    }
    splx(s);
    }
#elif __linux__
    res=wait_event_interruptible(sc->remoteirq_wait,
            irq_pending(sc, fd, data->irq_mask)
            );

    if (res) return EINTR;
#endif

    res=sis1100_irq_get(sc, fd, data);

    return res;
}
